package lock

import (
	"sync"
	"time"

	"gitee.com/shoyer/logger"
)

var (
	mapQueue   = map[string][]*QueueLockItem{}
	mapLock    = map[string]*sync.RWMutex{}
	lockForMap = &sync.RWMutex{}

	// 用内存性能很高，用redis的方式性能要低很多(压测发现有5倍差距), 而且不会存在加锁失败的时候需要重试的情况，建议用内存模式。
	// 缺点是，重启切换进程的时候，不同的进程锁不到同一个key, 所以在重启之前，需要把这个切到redis模式，重启完成后，再切成内存模式。
	useMemory = true // TODO: should read from config file. or a gloabl cache, can switch when reboot the server.
)

// QueueLockItem QueueLockItem
type QueueLockItem struct {
	Key    string
	Ch     chan bool
	Locked bool
}

// QueueLock QueueLock
func QueueLock(key string, ch chan bool) {
	lockForMap.Lock()
	defer lockForMap.Unlock()

	var queue, exist = mapQueue[key]
	var rw = mapLock[key]
	if !exist {
		mapQueue[key] = []*QueueLockItem{}
		mapLock[key] = &sync.RWMutex{}

		queue = mapQueue[key]
		rw = mapLock[key]
	}

	rw.Lock()
	defer rw.Unlock()

	mapQueue[key] = append(queue, &QueueLockItem{Key: key, Ch: ch, Locked: false})
	tryLock(key)
}

// ReleaseQueueLock ReleaseQueueLock
func ReleaseQueueLock(key string) {
	lockForMap.Lock()
	defer lockForMap.Unlock()

	var rw, isRWok = mapLock[key]
	if !isRWok {
		return
	}

	rw.Lock()
	defer rw.Unlock()

	if !useMemory {
		UnLock(key)
	}

	var index = 0
	var queue, ok = mapQueue[key]
	if ok {
		mapQueue[key] = append(queue[:index], queue[index+1:]...)
		tryLock(key)
	}
}

func tryLock(key string) {
	var queue, ok = mapQueue[key]
	if !ok {
		return
	}

	defer func() {
		if err := recover(); err != nil {
			logger.Error(err)
		}
	}()

	if len(queue) == 0 {
		delete(mapQueue, key)
		delete(mapLock, key)
		return
	}

	var first = queue[0]
	if first.Locked {
		return
	}

	if useMemory {
		first.Locked = true
		first.Ch <- true
	} else {
		// TODO: 这里如果失败，应该一直重试，直到成功为止，到达一定次数降级为内存方式
		if Lock(first.Key, time.Second*60) {
			first.Locked = true
			first.Ch <- true
		}
	}
}
